//
//  FyUILib.m
//  FyUILib
//
//  Created by luoc on 16/2/6.
//  Copyright © 2016年 luoc. All rights reserved.
//

#import "UIMarkup.h"
extern const char* CharNext(const char* pstr);


@interface CMarkup()
{
    XMLELEMENT*     _pElements;
    unsigned long   _nElements;
    unsigned long   _nReservedElements;
    char            _szErrorMsg[100];
    char            _szErrorXML[50];
    BOOL            _bPreserveWhitespace;
}
@end
@implementation CMarkup
- (int)GetXMLElementsiStart:(int)iPos
{
    return (int)_pElements[iPos].iStart;
}

- (int)GetXMLElementsiData:(int)iPos
{
    return (int)_pElements[iPos].iData;
}

- (int)GetXMLElementsiChild:(int)iPos
{
    return (int)_pElements[iPos].iChild;
}

- (int)GetXMLElementsiNext:(int)iPos
{
    return (int)_pElements[iPos].iNext;
}

- (int)GetXMLElementsiParent:(int)iPos
{
    return (int)_pElements[iPos].iParent;
}

- (char*)GetXmlStr
{
    return _pStrXML;
}

- (void)SetXml:(const char*)pstrXML
{
    _pStrXML = NULL;
    _pElements = NULL;
    _nElements = 0;
    _bPreserveWhitespace = YES;
    if( pstrXML != NULL ) [self Load:pstrXML];
}

- (BOOL)Load:(const char*)pstrXML
{
    [self Release];
    int cchLen = (int)strlen(pstrXML) + 1;
    _pStrXML = (char*)(malloc(cchLen * sizeof(char)));
    memcpy(_pStrXML, pstrXML, cchLen * sizeof(char));
    _pStrXML[cchLen-1] = 0;
    BOOL bRes = [self _Parse];
    if( !bRes ) [self Release];
    return bRes;
}

- (BOOL)LoadFromeFile:(const char*)pstrFileName
{
    [self Release];
    FILE* fp = fopen(pstrFileName, "r");
    if (fp) {
        fseek(fp, 0, SEEK_END);
        int dwSize = (int)ftell(fp);
        fseek(fp, 0, SEEK_SET);
    
        if( dwSize == 0 ) return [self _Failed:"File is empty" :NULL];
        if ( dwSize > 4096*1024 ) return [self _Failed:"File too large" :NULL];
        
        _pStrXML = (char*)malloc( dwSize + 1);
        int dwRead = (int)fread(_pStrXML, 1, dwSize, fp);
        
        if( dwRead != dwSize ) {
            [self Release];
            return [self _Failed:"Could not read file" :NULL];
        }
        
        fclose(fp);
        _pStrXML[dwSize] = 0;
        BOOL bRes = [self _Parse];
        if( !bRes ) [self Release];
        return bRes;
    }
    return NO;
}

- (void)Release
{
    if( _pStrXML != NULL ) free(_pStrXML);
    if( _pElements != NULL ) free(_pElements);
    _pStrXML = NULL;
    _pElements = NULL;
    _nElements = 0;
}

- (BOOL)IsValid
{
    return _pElements != NULL;;
}

- (void)SetPreserveWhitespace:(BOOL)bPreserve
{
    _bPreserveWhitespace = bPreserve;
}

- (void)GetLastErrorMessage:(const char*)pstrMessage :(int)cchMax
{
    strncpy((char*)pstrMessage, _szErrorMsg, cchMax);
}

- (void)GetLastErrorLocation:(const char*)pstrSource :(int)cchMax
{
    strncpy((char*)pstrSource, _szErrorMsg, cchMax);
}

- (CMarkupNode*)GetRoot
{
    if( _nElements == 0 ) {
        return [[CMarkupNode alloc] init];
    }
    CMarkupNode* node = [[CMarkupNode alloc] init];
    [node SetOwner:self pos:1];
    return node;
}

- (BOOL)_Parse
{
    [self _ReserveElement]; // Reserve index 0 for errors
    memset(_szErrorMsg, 0x00, sizeof(_szErrorMsg));
    memset(_szErrorXML, 0x00, sizeof(_szErrorXML));
    char* pstrXML = _pStrXML;
    return [self _Parse:&pstrXML :0];
}

- (BOOL)_Parse:(char**)pstrText :(unsigned long)iParent
{
    [self _SkipWhitespace:pstrText];
    unsigned long iPrevious = 0;
    for( ; ; )
    {
        if( *(*pstrText) == ('\0') && iParent <= 1 ) {
            return YES;
        }
        [self _SkipWhitespace:pstrText];
        if( *(*pstrText) != ('<') )
            return [self _Failed:("Expected start tag") :(*pstrText)];
        if( ((*pstrText))[1] == ('/') ) {
            return YES;
        }
        
        *(*pstrText)++ = ('\0');
        [self _SkipWhitespace:pstrText];
        // Skip comment or processing directive
        if( *(*pstrText) == ('!') || *(*pstrText) == ('?') ) {
            char ch = *(*pstrText);
            if( *(*pstrText) == ('!') ) ch = ('-');
            while( *(*pstrText) != ('\0') && !(*(*pstrText) == ch && *((*pstrText) + 1) == ('>')) ) (*pstrText) = (char*)CharNext(*pstrText);
            if( *(*pstrText) != ('\0') ) (*pstrText) += 2;
            [self _SkipWhitespace:pstrText];
            continue;
        }
        [self _SkipWhitespace:pstrText];
        // Fill out element structure
        XMLELEMENT* pEl = [self _ReserveElement];
        unsigned long iPos = pEl - _pElements;
        pEl->iStart = (*pstrText) - _pStrXML;
        pEl->iParent = iParent;
        pEl->iNext = pEl->iChild = 0;
        if( iPrevious != 0 ) _pElements[iPrevious].iNext = iPos;
        else if( iParent > 0 ) _pElements[iParent].iChild = iPos;
        iPrevious = iPos;
        // Parse name
        const char* pstrName = (*pstrText);
        [self _SkipIdentifier:pstrText];
        char* pstrNameEnd = (*pstrText);
        if( *(*pstrText) == ('\0') ) return [self _Failed:("Error parsing element name") :(*pstrText)];
        // Parse attributes
        if( ![self _ParseAttributes:pstrText] ) return NO;
        [self _SkipWhitespace:pstrText];
        if( (*pstrText)[0] == ('/') && (*pstrText)[1] == ('>') )
        {
            pEl->iData = (*pstrText) - _pStrXML;
            *(*pstrText) = ('\0');
            (*pstrText) += 2;
        }
        else
        {
            if( *(*pstrText) != ('>') ) return [self _Failed:("Expected start-tag closing") :(*pstrText)];
            // Parse node data
            pEl->iData = ++(*pstrText) - _pStrXML;
            char* pstrDest = (*pstrText);
            if( ![self _ParseData:pstrText :&pstrDest :'<'] ) return NO;
            // Determine type of next element
            if( *(*pstrText) == ('\0') && iParent <= 1 ) {
                return YES;
            }
            if( *(*pstrText) != ('<') ) return [self _Failed:("Expected end-tag start") :(*pstrText)];
            if( (*pstrText)[0] == ('<') && (*pstrText)[1] != ('/') )
            {
                if( ![self _Parse:pstrText :iPos] ) return NO;
            }
            if( (*pstrText)[0] == ('<') && (*pstrText)[1] == ('/') )
            {
                *pstrDest = ('\0');
                *(*pstrText) = ('\0');
                (*pstrText) += 2;
                [self _SkipWhitespace:pstrText];
                int cchName = (int)(pstrNameEnd - pstrName);
                if( strncmp((*pstrText), pstrName, cchName) != 0 ) return [self _Failed:("Unmatched closing tag") :(*pstrText)];
                (*pstrText) += cchName;
                [self _SkipWhitespace:pstrText];
                
                if( *(*pstrText)++ != ('>') ) return [self _Failed:("Unmatched closing tag") :(*pstrText)];
            }
        }
        *pstrNameEnd = ('\0');
        [self _SkipWhitespace:pstrText];
    }
    return NO;
}

- (XMLELEMENT*)_ReserveElement
{
    if( _nElements == 0 ) _nReservedElements = 0;
    if( _nElements >= _nReservedElements ) {
        _nReservedElements += (_nReservedElements / 2) + 500;
        _pElements = (XMLELEMENT*)(realloc(_pElements, _nReservedElements * sizeof(XMLELEMENT)));
    }
    return &_pElements[_nElements++];
}

- (void)_SkipWhitespace:(char**)pstr
{
    while( *(*pstr) > ('\0') && *(*pstr) <= (' ') ) *pstr = (char*)CharNext(*pstr);
}

- (void)_SkipIdentifier:(char**)pstr
{
    while( *(*pstr) != ('\0') && (*(*pstr) == ('_') || *(*pstr) == (':') || isalnum(*(*pstr))) )
        *pstr = (char*)CharNext(*pstr);
}

- (BOOL)_ParseData:(char**)pstrText :(char**)pstrDest :(char)cEnd
{
    while( *(*pstrText) != ('\0') && *(*pstrText) != cEnd ) {
        if( **pstrText == ('&') ) {
            while( *(*pstrText) == ('&') ) {
                (*pstrText)++;
                [self _ParseMetaChar:pstrText :pstrDest];
                
            }
            if (*(*pstrText) == cEnd)
                break;
        }
        
        if( *(*pstrText) == (' ') ) {
            *(*pstrDest)++ = *(*pstrText)++;
            if( !_bPreserveWhitespace ) [self _SkipWhitespace:pstrText];;
        }
        else {
            char* pstrTemp = (char*)CharNext(*pstrText);
            while( *pstrText < pstrTemp) {
                *(*pstrDest)++ = *(*pstrText)++;
            }
        }
    }
    // Make sure that MapAttributes() works correctly when it parses
    // over a value that has been transformed.
    char* pstrFill = *pstrDest + 1;
    while( pstrFill < *pstrText ) *pstrFill++ = (' ');
    return YES;
}

- (void)_ParseMetaChar:(char**)pstrText :(char**)pstrDest
{
    if( (*pstrText)[0] == ('a') && (*pstrText)[1] == ('m') && (*pstrText)[2] == ('p') && (*pstrText)[3] == (';') ) {
        *(*pstrDest)++ = ('&');
        *pstrText += 4;
    }
    else if( (*pstrText)[0] == ('l') && (*pstrText)[1] == ('t') && (*pstrText)[2] == (';') ) {
        *(*pstrDest)++ = ('<');
        *pstrText += 3;
    }
    else if( (*pstrText)[0] == ('g') && (*pstrText)[1] == ('t') && (*pstrText)[2] == (';') ) {
        *(*pstrDest)++ = ('>');
        *pstrText += 3;
    }
    else if( (*pstrText)[0] == ('q') && (*pstrText)[1] == ('u') && (*pstrText)[2] == ('o') && (*pstrText)[3] == ('t') && (*pstrText)[4] == (';') ) {
        *(*pstrDest)++ = ('\"');
        *pstrText += 5;
    }
    else if( (*pstrText)[0] == ('a') && (*pstrText)[1] == ('p') && (*pstrText)[2] == ('o') && (*pstrText)[3] == ('s') && (*pstrText)[4] == (';') ) {
        *(*pstrDest)++ = ('\'');
        *pstrText += 5;
    }else if ((*pstrText)[0] == ('#') && (*pstrText)[1] == ('x') && (*pstrText)[2] == ('0') && (*pstrText)[3] == ('0') && (*pstrText)[4] == ('0') && (*pstrText)[5] == ('A') && (*pstrText)[6] == (';')){
        *(*pstrDest)++ = ('\n');
        *pstrText += 7;
    }else {
        *(*pstrDest)++ = ('&');
    }
}

- (BOOL)_ParseAttributes:(char**)pstrText
{
    if( *(*pstrText) == ('>') ) return YES;
    *(*pstrText)++ = '\0';
    [self _SkipWhitespace:pstrText];
    while( *(*pstrText) != ('\0') && *(*pstrText) != ('>') && *(*pstrText) != ('/') ) {
        [self _SkipIdentifier:pstrText];
        char* pstrIdentifierEnd = *pstrText;
        [self _SkipWhitespace:pstrText];
        if( *(*pstrText) != ('=') ) return [self _Failed:("Error while parsing attributes") :*pstrText];
        *(*pstrText)++ = ' ';
        *pstrIdentifierEnd = ('\0');
        [self _SkipWhitespace:pstrText];
        
        if( *(*pstrText)++ != ('\"') ) return [self _Failed:("Expected attribute value") :*pstrText];
        
        char* pstrDest = *pstrText;
        if( ![self _ParseData:pstrText :&pstrDest :('\"')] ) return NO;
        if( *(*pstrText) == ('\0') ) return [self _Failed:("Error while parsing attribute string") :*pstrText];
        *pstrDest = ('\0');
        if( *pstrText != pstrDest ) *(*pstrText) = (' ');
        
        (*pstrText)++;
        [self _SkipWhitespace:pstrText];
    }
    return YES;
}

- (BOOL)_Failed:(const char*)pstrError :(const char*)pstrLocation
{
    NSLog(@"XML Error: %s", pstrError);
    if( pstrLocation != NULL ) NSLog(@"%s", pstrLocation);
    //strncpy(_szErrorMsg, pstrError, (sizeof(_szErrorMsg) / sizeof(_szErrorMsg[0])) - 1);
    //strncpy(_szErrorXML, pstrLocation != NULL ? pstrLocation : (""), strlen(_szErrorXML) - 1);
    return NO; // Always return 'false'
}
@end


typedef struct
{
    unsigned long iName;
    unsigned      iValue;
} XMLATTRIBUTE;
enum { MAX_XML_ATTRIBUTES = 64 };

@interface CMarkupNode()
{
    int             _iPos;
    int             _nAttributes;
    XMLATTRIBUTE    _aAttributes[MAX_XML_ATTRIBUTES];
    CMarkup*        _pOwner;

}
@end
@implementation CMarkupNode
- (id)init{
    self = [super init];
    if (self) {
        _iPos = 0;
        _pOwner = nil;
        _nAttributes = 0;
    }
    return self;
}

- (void)SetOwner:(CMarkup*)pOwner pos:(int)iPos
{
    _pOwner = pOwner;
    _iPos = iPos;
}

- (BOOL)IsValid
{
    return (_pOwner != NULL);
}

- (CMarkupNode*)GetParent
{
    CMarkupNode* node = [[CMarkupNode alloc] init];
    if( _pOwner == NULL ) return node;
    unsigned int iPos = [_pOwner GetXMLElementsiParent:_iPos];
    if( iPos == 0 ) return node;
    
    [node SetOwner:_pOwner pos:iPos];
    return node;
}

- (CMarkupNode*)GetSibling
{
    CMarkupNode* node = [[CMarkupNode alloc] init];
    if( _pOwner == NULL ) return node;
    unsigned int iPos = [_pOwner GetXMLElementsiNext:_iPos];
    if( iPos == 0 ) return node;
    
    [node SetOwner:_pOwner pos:iPos];
    return node;
}

- (CMarkupNode*)GetChild
{
    CMarkupNode* node = [[CMarkupNode alloc] init];
    if( _pOwner == NULL ) return node;
    unsigned int iPos = [_pOwner GetXMLElementsiChild:_iPos];
    if( iPos == 0 ) return node;
    
    [node SetOwner:_pOwner pos:iPos];
    return node;
}

- (CMarkupNode*)GetChild:(const char*)pstrName
{
    CMarkupNode* node = [[CMarkupNode alloc] init];
    if( _pOwner == NULL ) return node;
    unsigned int iPos = [_pOwner GetXMLElementsiChild:_iPos];
    while( iPos != 0 ) {
        if( strcmp([_pOwner GetXmlStr] + [_pOwner GetXMLElementsiStart:iPos], pstrName) == 0 ) {
            [node SetOwner:_pOwner pos:iPos];
            return node;
        }
        iPos = [_pOwner GetXMLElementsiNext:iPos];
    }
    return node;
}

- (BOOL)HasSiblings
{
    if( _pOwner == NULL ) return false;
    unsigned long iPos = [_pOwner GetXMLElementsiNext:_iPos];
    return iPos > 0;
}

- (BOOL)HasChildren
{
    if( _pOwner == NULL ) return NO;
    return [_pOwner GetXMLElementsiChild:_iPos];
}

- (const char*)GetName
{
    if( _pOwner == NULL ) return NULL;
    char* pChar = [_pOwner GetXmlStr];
    return pChar + [_pOwner GetXMLElementsiStart:_iPos];
}

- (const char*)GetValue
{
    if( _pOwner == NULL ) return NULL;
    char* pChar = [_pOwner GetXmlStr];
    return pChar + [_pOwner GetXMLElementsiData:_iPos];
}

- (BOOL)HasAttributes
{
    if( _pOwner == NULL ) return NO;
    if( _nAttributes == 0 ) [self _MapAttributes];
    return _nAttributes > 0;
}

- (BOOL)HasAttribute:(const char*)pstrName
{
    if( _pOwner == NULL ) return NO;
    if( _nAttributes == 0 ) [self _MapAttributes];
    for( int i = 0; i < _nAttributes; i++ ) {
        if( strcmp([_pOwner GetXmlStr] + _aAttributes[i].iName, pstrName) == 0 ) return YES;
    }
    return NO;
}

- (int)GetAttributeCount
{
    if( _pOwner == NULL ) return 0;
    if( _nAttributes == 0 ) [self _MapAttributes];
    return _nAttributes;
}

- (const char*)GetAttributeName:(int)index
{
    if( _pOwner == NULL ) return NULL;
    if( _nAttributes == 0 ) [self _MapAttributes];
    if( index < 0 || index >= _nAttributes ) return ("");
    return [_pOwner GetXmlStr] + _aAttributes[index].iName;
}

- (const char*)GetAttributeValueWithIndex:(int)iIndex
{
    if( _pOwner == NULL ) return NULL;
    if( _nAttributes == 0 ) [self _MapAttributes];
    if( iIndex < 0 || iIndex >= _nAttributes ) return ("");
    return [_pOwner GetXmlStr] + _aAttributes[iIndex].iValue;
}

- (const char*)GetAttributeValueWithName:(const char*)pstrName
{
    if( _pOwner == NULL ) return NULL;
    if( _nAttributes == 0 ) [self _MapAttributes];
    for( int i = 0; i < _nAttributes; i++ ) {
        if( strcmp([_pOwner GetXmlStr] + _aAttributes[i].iName, pstrName) == 0 ) return [_pOwner GetXmlStr] + _aAttributes[i].iValue;
    }
    return ("");
}

- (BOOL)GetAttributeValueWithIndex:(int)iIndex :(char*)pstrValue :(int)cchMax
{
    if( _pOwner == NULL ) return NO;
    if( _nAttributes == 0 ) [self _MapAttributes];
    if( iIndex < 0 || iIndex >= _nAttributes ) return NO;
    strncpy(pstrValue, [_pOwner GetXmlStr] + _aAttributes[iIndex].iValue, cchMax);
    return YES;
}

- (BOOL)GetAttributeValueWithName:(char*)pstrName :(char*)pstrValue :(int)cchMax
{
    if( _pOwner == NULL ) return NO;
    if( _nAttributes == 0 ) [self _MapAttributes];
    for( int i = 0; i < _nAttributes; i++ ) {
        if( strcmp([_pOwner GetXmlStr] + _aAttributes[i].iName, pstrName) == 0 ) {
            strncpy(pstrValue, [_pOwner GetXmlStr] + _aAttributes[i].iValue, cchMax);
            return YES;
        }
    }
    return NO;
}

- (void)_MapAttributes
{
    _nAttributes = 0;
    char* pstr = [_pOwner GetXmlStr] + [_pOwner GetXMLElementsiStart:_iPos];
    char* pstrEnd = [_pOwner GetXmlStr] + [_pOwner GetXMLElementsiData:_iPos];
    pstr += strlen(pstr) + 1;
    while( pstr < pstrEnd ) {
        [_pOwner _SkipWhitespace:&pstr];
        _aAttributes[_nAttributes].iName = pstr - [_pOwner GetXmlStr];
        pstr += strlen(pstr) + 1;
        [_pOwner _SkipWhitespace:&pstr];
        if( *pstr++ != ('\"') ) return; // if( *pstr != _A('\"') ) { pstr = ::CharNext(pstr); return; }
        
        _aAttributes[_nAttributes++].iValue = (int)(pstr - [_pOwner GetXmlStr]);
        if( _nAttributes >= MAX_XML_ATTRIBUTES ) return;
        pstr += strlen(pstr) + 1;
    }
}
@end
